<!DOCTYPE html>
<!-- saved from url=(0080)https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5c10c75af265da6135726f6c -->
<html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover"><meta name="google-site-verification" content="cCHsgG9ktuCTgWgYfqCJql8AeR4gAne4DTZqztPoirE"><meta name="apple-itunes-app" content="app-id=987739104"><meta name="baidu-site-verification" content="qiK2a1kcFc"><meta name="360-site-verification" content="4c3c7d57d59f0e1a308462fbc7fd7e51"><meta name="sogou_site_verification" content="c49WUDZczQ"><style>body {
        font-size: 16px;
        line-height: 2;
      }
      a, button, input {
        margin: 1rem 1.5rem;
      }
      img {
        width: 0;
        height: 0;
      }
      #juejin {
        overflow-x: hidden;
      }</style><title data-vue-meta="true">前端面试之道 - yck - 掘金小册</title><link rel="apple-touch-icon" sizes="180x180" href="https://b-gold-cdn.xitu.io/favicons/v2/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="https://b-gold-cdn.xitu.io/favicons/v2/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="https://b-gold-cdn.xitu.io/favicons/v2/favicon-16x16.png"><link rel="manifest" href="https://b-gold-cdn.xitu.io/favicons/v2/manifest.json"><link rel="mask-icon" href="https://b-gold-cdn.xitu.io/favicons/v2/safari-pinned-tab.svg" color="#5bbad5"><link rel="shortcut icon" href="https://b-gold-cdn.xitu.io/favicons/v2/favicon.ico"><meta name="msapplication-config" content="https://b-gold-cdn.xitu.io/favicons/v2/browserconfig.xml"><meta name="theme-color" content="#ffffff"><link rel="search" title="掘金" href="https://b-gold-cdn.xitu.io/conf/search.xml" type="application/opensearchdescription+xml"><link rel="stylesheet" href="./18-实现小型打包工具_files/ionicons.min.css"><link rel="stylesheet" href="./18-实现小型打包工具_files/iconfont.css"><link href="./18-实现小型打包工具_files/0.20e96f0e16539d696fbd.css" rel="stylesheet"><script async="" src="./18-实现小型打包工具_files/hm.js.下载"></script><script async="" src="./18-实现小型打包工具_files/analytics.js.下载"></script><script type="text/javascript" async="" src="./18-实现小型打包工具_files/vds.js.下载"></script><script charset="utf-8" src="./18-实现小型打包工具_files/13.46a6fc9d409a46c2798c.js.下载"></script><meta data-vmid="keywords" name="keywords" content="掘金,稀土,Vue.js,微信小程序,Kotlin,RxJava,React Native,Wireshark,敏捷开发,Bootstrap,OKHttp,正则表达式,WebGL,Webpack,Docker,MVVM" data-vue-meta="true"><meta data-vmid="description" name="description" content="掘金是一个帮助开发者成长的社区，是给开发者用的 Hacker News，给设计师用的 Designer News，和给产品经理用的 Medium。掘金的技术文章由稀土上聚集的技术大牛和极客共同编辑为你筛选出最优质的干货，其中包括：Android、iOS、前端、后端等方面的内容。用户每天都可以在这里找到技术世界的头条内容。与此同时，掘金内还有沸点、掘金翻译计划、线下活动、专栏文章等内容。即使你是 GitHub、StackOverflow、开源中国的用户，我们相信你也可以在这里有所收获。" data-vue-meta="true"></head><body><div id="juejin" data-v-514f306c=""><div class="global-component-box" data-v-514f306c=""><!----><div data-v-71cabb60="" data-v-514f306c="" class="alert-list alert-list"></div><!----><!----><!----><div class="emoji-barrage" data-v-e5f49d52="" data-v-514f306c=""><!----></div><div class="book-new-user-award-popup" style="display:none;" data-v-71b0e7b2="" data-v-514f306c=""><div class="content-box" style="display:;" data-v-71b0e7b2=""><div class="close ion-close-round" data-v-71b0e7b2=""></div><div class="header" data-v-71b0e7b2=""><div class="icon" data-v-71b0e7b2=""><img src="./18-实现小型打包工具_files/icon.a87e5ae.svg" data-v-71b0e7b2=""></div><div class="txt" data-v-71b0e7b2="">新人专享好礼</div></div><div class="desc" data-v-71b0e7b2="">凡未购买过小册的用户，均可领取三张 5 折新人专享券，购买小册时自动使用专享券，最高可节省 45 元。</div><div class="tickets" data-v-71b0e7b2=""><div class="ticket" data-v-71b0e7b2=""><div class="ticket__inner" data-v-71b0e7b2=""><div class="enjoy" data-v-71b0e7b2=""><span class="new-title" data-v-71b0e7b2="">小册新人 5 折券</span></div><div class="sale" data-v-71b0e7b2="">最高可省 15 元</div></div></div><div class="ticket" data-v-71b0e7b2=""><div class="ticket__inner" data-v-71b0e7b2=""><div class="enjoy" data-v-71b0e7b2=""><span class="new-title" data-v-71b0e7b2="">小册新人 5 折券</span></div><div class="sale" data-v-71b0e7b2="">最高可省 15 元</div></div></div><div class="ticket" data-v-71b0e7b2=""><div class="ticket__inner" data-v-71b0e7b2=""><div class="enjoy" data-v-71b0e7b2=""><span class="new-title" data-v-71b0e7b2="">小册新人 5 折券</span></div><div class="sale" data-v-71b0e7b2="">最高可省 15 元</div></div></div></div><div class="remark" data-v-71b0e7b2="">注：专享券的使用期限在领券的七天内。</div><div class="submit-btn" data-v-71b0e7b2="">一键领取</div></div><div class="model success" style="display:none;" data-v-71b0e7b2=""><div class="heading" data-v-71b0e7b2="">领取成功</div><div class="content-text" data-v-71b0e7b2="">购买小册时自动使用专享券</div><div class="btn-success-footer" data-v-71b0e7b2=""><div class="btn-ok" data-v-71b0e7b2="">知道了</div><div class="btn-ok btn-link" data-v-71b0e7b2="">前往小册首页</div></div></div><div class="model fail" style="display:none;" data-v-71b0e7b2=""><div class="heading" data-v-71b0e7b2="">领取失败</div><div class="content-text" data-v-71b0e7b2="">本活动仅适用于小册新用户</div><div class="btn-ok" data-v-71b0e7b2="">知道了</div></div></div><!----></div><!----><div data-v-097468bb="" data-v-514f306c="" class="book-read-view"><section data-v-097468bb="" class="book-section"><div data-v-097468bb="" class="book-summary"><div data-v-097468bb="" class="book-summary-masker"></div><div data-v-097468bb="" class="book-summary-inner"><div data-v-097468bb="" class="book-summary__header"><a data-v-097468bb="" href="https://juejin.im/books" target="" rel="" class="logo"><img data-v-097468bb="" src="./18-实现小型打包工具_files/logo.a7995ad.svg"></a><div data-v-097468bb="" class="label">小册</div><!----></div><!----><div data-v-d0eb2184="" data-v-097468bb="" class="book-directory book-directory bought"><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">1</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">小册食用指南</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">2</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">JS 基础知识点及常考面试题（一）</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">3</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">JS 基础知识点及常考面试题（二）</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">4</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">ES6 知识点及常考面试题</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">5</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">JS 异步编程及常考面试题</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">6</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">手写 Promise</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">7</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">Event Loop</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">8</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">JS 进阶知识点及常考面试题</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">9</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">JS 思考题</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">10</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">DevTools Tips</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">11</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">浏览器基础知识点及常考面试题</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">12</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">浏览器缓存机制</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">13</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">浏览器渲染原理</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">14</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">安全防范知识点</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">15</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">从 V8 中看 JS 性能优化</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">16</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">性能优化琐碎事</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link read"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">17</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">Webpack 性能优化</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section route-active section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">18</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">实现小型打包工具</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">19</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">React 和 Vue 两大框架之间的相爱相杀</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">20</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">Vue 常考基础知识点</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">21</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">Vue 常考进阶知识点</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">22</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">React 常考基础知识点</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">23</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">React 常考进阶知识点</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">24</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">监控</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">25</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">UDP</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">26</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">TCP</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">27</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">HTTP 及 TLS</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">28</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">HTTP/2 及 HTTP/3</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">29</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">输入 URL 到页面渲染的整个流程</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">30</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">设计模式</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">31</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">常见数据结构</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">32</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">常考算法题解析</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">33</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">CSS 常考面试题资料</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">34</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">如何写好一封简历</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">35</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">面试常用技巧</div><!----><!----></div><!----></a><a data-v-d0eb2184="" class="section section-link"><div data-v-d0eb2184="" class="step"><div data-v-d0eb2184="" class="step-btn">36</div></div><div data-v-d0eb2184="" class="center"><div data-v-d0eb2184="" class="title">前方的路，让我们结伴同行</div><!----><!----></div><!----></a></div><div data-v-097468bb="" class="book-summary__footer"><div data-v-097468bb="" class="qr-icon"><img data-v-097468bb="" src="./18-实现小型打包工具_files/qr-icon.881015a.svg"></div><div data-v-097468bb="" class="qr-tips"><span data-v-097468bb="" class="ion-close"></span><div data-v-097468bb="" class="title"><span data-v-097468bb="">关注公众号</span><span data-v-097468bb="">领取优惠码</span></div><div data-v-097468bb="" class="qr-img"><img data-v-097468bb="" src="./18-实现小型打包工具_files/wx-qr.13d8b4d.png"></div></div></div></div></div><div data-v-097468bb="" class="book-content"><div data-v-097468bb="" class="book-content-inner"><div data-v-097468bb="" class="book-content__header"><div data-v-097468bb="" class="switch"><img data-v-097468bb="" src="./18-实现小型打包工具_files/icon.3e69d5a.svg"></div><div data-v-097468bb="" class="menu"><img data-v-097468bb="" src="./18-实现小型打包工具_files/menu.74b9add.svg"></div><div data-v-097468bb="" class="title"><a data-v-097468bb="" href="https://juejin.im/book/5bdc715fe51d454e755f75ef" target="" rel="">前端面试之道</a></div><div data-v-629272fe="" data-v-097468bb="" class="user-auth user-auth"><div data-v-629272fe="" class="nav-item menu"><div data-v-3b1ba6d2="" data-v-afcc6e34="" data-v-629272fe="" data-src="https://b-gold-cdn.xitu.io/v3/static/img/default-avatar.e30559a.svg" class="lazy avatar avatar loaded" style="background-image: url(&quot;https://b-gold-cdn.xitu.io/v3/static/img/default-avatar.e30559a.svg&quot;);"></div><div data-v-629272fe="" class="nav-menu user-dropdown-list" style="display: none;"><ul data-v-629272fe="" class="nav-menu-item-group"><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe=""><i data-v-629272fe="" class="fengwei fw-write"></i><span data-v-629272fe="">写文章</span></a></li><!----><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe=""><i data-v-629272fe="" class="fengwei fw-draft"></i><span data-v-629272fe="">草稿</span></a></li></ul><ul data-v-629272fe="" class="nav-menu-item-group"><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/user/5c2c54ca6fb9a049cb18da5a"><i data-v-629272fe="" class="fengwei fw-person"></i><span data-v-629272fe="">我的主页</span></a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/user/5c2c54ca6fb9a049cb18da5a/likes"><i data-v-629272fe="" class="fengwei fw-like"></i><span data-v-629272fe="">我喜欢的</span></a></li><!----><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/user/5c2c54ca6fb9a049cb18da5a/collections"><i data-v-629272fe="" class="fengwei fw-collection"></i><span data-v-629272fe="">我的收藏集</span></a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/user/5c2c54ca6fb9a049cb18da5a/books?type=bought"><i data-v-629272fe="" class="fengwei fw-bought"></i><span data-v-629272fe="">已购</span></a></li><!----><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/subscribe"><i data-v-629272fe="" class="fengwei fw-tag"></i><span data-v-629272fe="">标签管理</span></a></li></ul><ul data-v-629272fe="" class="nav-menu-item-group"><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/user/settings"><i data-v-629272fe="" class="fengwei fw-setting"></i><span data-v-629272fe="">设置</span></a></li><li data-v-629272fe="" class="nav-menu-item more"><a data-v-629272fe=""><i data-v-629272fe="" class="fengwei fw-info"></i><span data-v-629272fe="">关于</span><i data-v-629272fe="" class="ion-chevron-right more-icon"></i></a><div data-v-629272fe="" class="nav-menu more-dropdown-list"><ul data-v-629272fe="" class="nav-menu-item-group"><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/app" target="_blank">下载应用</a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://juejin.im/about" target="_blank">关于</a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://xitu.io/jobs" target="_blank">加入我们</a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://github.com/xitu/gold-miner" rel="nofollow noopener noreferrer" target="_blank">翻译计划</a></li><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe="" href="https://bd.juejin.im/?utm_campaign=bd&amp;utm_source=web&amp;utm_medium=nav" target="_blank">合作伙伴</a></li></ul></div></li></ul><ul data-v-629272fe="" class="nav-menu-item-group"><li data-v-629272fe="" class="nav-menu-item"><a data-v-629272fe=""><i data-v-629272fe="" class="fengwei fw-logout"></i><span data-v-629272fe="">登出</span></a></li></ul></div></div><!----></div><!----></div><div data-v-097468bb="" class="book-body transition--next"><div data-v-5602b343="" data-v-097468bb="" class="section-view book-section-content"><div data-v-05bbf43a="" data-v-5602b343="" class="section-content"><div data-v-05bbf43a="" class="section-page book-section-view"><div data-v-05bbf43a="" class="entry-content article-content"><h1 class="heading" data-id="heading-0">实现小型打包工具</h1>
<p>原本小册计划中是没有这一章节的，Webpack 工作原理应该是上一章节包含的内容。但是考虑到既然讲到工作原理，必然需要讲解源码，但是 Webpack 的源码很难读，不结合源码干巴巴讲原理又没有什么价值。所以在这一章节中，我将会带大家来实现一个几十行的迷你打包工具，该工具可以实现以下两个功能</p>
<ul>
<li>将 ES6 转换为 ES5</li>
<li>支持在 JS 文件中 <code>import</code> CSS 文件</li>
</ul>
<p>通过这个工具的实现，大家可以理解到打包工具的<strong>原理</strong>到底是什么。</p>
<h2 class="heading" data-id="heading-1">实现</h2>
<p>因为涉及到 ES6 转 ES5，所以我们首先需要安装一些 Babel 相关的工具</p>
<pre><code class="hljs bash" lang="bash">yarn add babylon babel-traverse babel-core babel-preset-env  
</code></pre><p>接下来我们将这些工具引入文件中</p>
<pre><code class="hljs js" lang="js"><span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)
<span class="hljs-keyword">const</span> babylon = <span class="hljs-built_in">require</span>(<span class="hljs-string">'babylon'</span>)
<span class="hljs-keyword">const</span> traverse = <span class="hljs-built_in">require</span>(<span class="hljs-string">'babel-traverse'</span>).default
<span class="hljs-keyword">const</span> { transformFromAst } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'babel-core'</span>)
</code></pre><p>首先，我们先来实现如何使用 Babel 转换代码</p>
<pre><code class="hljs js" lang="js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readCode</span>(<span class="hljs-params">filePath</span>) </span>{
  <span class="hljs-comment">// 读取文件内容</span>
  <span class="hljs-keyword">const</span> content = fs.readFileSync(filePath, <span class="hljs-string">'utf-8'</span>)
  <span class="hljs-comment">// 生成 AST</span>
  <span class="hljs-keyword">const</span> ast = babylon.parse(content, {
    <span class="hljs-attr">sourceType</span>: <span class="hljs-string">'module'</span>
  })
  <span class="hljs-comment">// 寻找当前文件的依赖关系</span>
  <span class="hljs-keyword">const</span> dependencies = []
  traverse(ast, {
    <span class="hljs-attr">ImportDeclaration</span>: <span class="hljs-function">(<span class="hljs-params">{ node }</span>) =&gt;</span> {
      dependencies.push(node.source.value)
    }
  })
  <span class="hljs-comment">// 通过 AST 将代码转为 ES5</span>
  <span class="hljs-keyword">const</span> { code } = transformFromAst(ast, <span class="hljs-literal">null</span>, {
    <span class="hljs-attr">presets</span>: [<span class="hljs-string">'env'</span>]
  })
  <span class="hljs-keyword">return</span> {
    filePath,
    dependencies,
    code
  }
}
</code></pre><ul>
<li>首先我们传入一个文件路径参数，然后通过 <code>fs</code> 将文件中的内容读取出来</li>
<li>接下来我们通过 <code>babylon</code> 解析代码获取 AST，目的是为了分析代码中是否还引入了别的文件</li>
<li>通过 <code>dependencies</code> 来存储文件中的依赖，然后再将 AST 转换为 ES5 代码</li>
<li>最后函数返回了一个对象，对象中包含了当前文件路径、当前文件依赖和当前文件转换后的代码</li>
</ul>
<p>接下来我们需要实现一个函数，这个函数的功能有以下几点</p>
<ul>
<li>调用 <code>readCode</code> 函数，传入入口文件</li>
<li>分析入口文件的依赖</li>
<li>识别 JS 和 CSS 文件</li>
</ul>
<pre><code class="hljs js" lang="js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDependencies</span>(<span class="hljs-params">entry</span>) </span>{
  <span class="hljs-comment">// 读取入口文件</span>
  <span class="hljs-keyword">const</span> entryObject = readCode(entry)
  <span class="hljs-keyword">const</span> dependencies = [entryObject]
  <span class="hljs-comment">// 遍历所有文件依赖关系</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> asset <span class="hljs-keyword">of</span> dependencies) {
    <span class="hljs-comment">// 获得文件目录</span>
    <span class="hljs-keyword">const</span> dirname = path.dirname(asset.filePath)
    <span class="hljs-comment">// 遍历当前文件依赖关系</span>
    asset.dependencies.forEach(<span class="hljs-function"><span class="hljs-params">relativePath</span> =&gt;</span> {
      <span class="hljs-comment">// 获得绝对路径</span>
      <span class="hljs-keyword">const</span> absolutePath = path.join(dirname, relativePath)
      <span class="hljs-comment">// CSS 文件逻辑就是将代码插入到 `style` 标签中</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/\.css$/</span>.test(absolutePath)) {
        <span class="hljs-keyword">const</span> content = fs.readFileSync(absolutePath, <span class="hljs-string">'utf-8'</span>)
        <span class="hljs-keyword">const</span> code = <span class="hljs-string">`
          const style = document.createElement('style')
          style.innerText = <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(content).replace(<span class="hljs-regexp">/\\r\\n/g</span>, <span class="hljs-string">''</span>)}</span>
          document.head.appendChild(style)
        `</span>
        dependencies.push({
          <span class="hljs-attr">filePath</span>: absolutePath,
          relativePath,
          <span class="hljs-attr">dependencies</span>: [],
          code
        })
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// JS 代码需要继续查找是否有依赖关系</span>
        <span class="hljs-keyword">const</span> child = readCode(absolutePath)
        child.relativePath = relativePath
        dependencies.push(child)
      }
    })
  }
  <span class="hljs-keyword">return</span> dependencies
}
</code></pre><ul>
<li>首先我们读取入口文件，然后创建一个数组，该数组的目的是存储代码中涉及到的所有文件</li>
<li>接下来我们遍历这个数组，一开始这个数组中只有入口文件，在遍历的过程中，如果入口文件有依赖其他的文件，那么就会被 <code>push</code> 到这个数组中</li>
<li>在遍历的过程中，我们先获得该文件对应的目录，然后遍历当前文件的依赖关系</li>
<li>在遍历当前文件依赖关系的过程中，首先生成依赖文件的绝对路径，然后判断当前文件是 CSS 文件还是 JS 文件
<ul>
<li>如果是 CSS 文件的话，我们就不能用 Babel 去编译了，只需要读取 CSS 文件中的代码，然后创建一个 <code>style</code> 标签，将代码插入进标签并且放入 <code>head</code> 中即可</li>
<li>如果是 JS 文件的话，我们还需要分析 JS 文件是否还有别的依赖关系</li>
<li>最后将读取文件后的对象 <code>push</code> 进数组中</li>
</ul>
</li>
</ul>
<p>现在我们已经获取到了所有的依赖文件，接下来就是实现打包的功能了</p>
<pre><code class="hljs js" lang="js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bundle</span>(<span class="hljs-params">dependencies, entry</span>) </span>{
  <span class="hljs-keyword">let</span> modules = <span class="hljs-string">''</span>
  <span class="hljs-comment">// 构建函数参数，生成的结构为</span>
  <span class="hljs-comment">// { './entry.js': function(module, exports, require) { 代码 } }</span>
  dependencies.forEach(<span class="hljs-function"><span class="hljs-params">dep</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> filePath = dep.relativePath || entry
    modules += <span class="hljs-string">`'<span class="hljs-subst">${filePath}</span>': (
      function (module, exports, require) { <span class="hljs-subst">${dep.code}</span> }
    ),`</span>
  })
  <span class="hljs-comment">// 构建 require 函数，目的是为了获取模块暴露出来的内容</span>
  <span class="hljs-keyword">const</span> result = <span class="hljs-string">`
    (function(modules) {
      function require(id) {
        const module = { exports : {} }
        modules[id](module, module.exports, require)
        return module.exports
      }
      require('<span class="hljs-subst">${entry}</span>')
    })({<span class="hljs-subst">${modules}</span>})
  `</span>
  <span class="hljs-comment">// 当生成的内容写入到文件中</span>
  fs.writeFileSync(<span class="hljs-string">'./bundle.js'</span>, result)
}
</code></pre><p>这段代码需要结合着 Babel 转换后的代码来看，这样大家就能理解为什么需要这样写了</p>
<pre><code class="hljs js" lang="js"><span class="hljs-comment">// entry.js</span>
<span class="hljs-keyword">var</span> _a = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./a.js'</span>)
<span class="hljs-keyword">var</span> _a2 = _interopRequireDefault(_a)
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_interopRequireDefault</span>(<span class="hljs-params">obj</span>) </span>{
    <span class="hljs-keyword">return</span> obj &amp;&amp; obj.__esModule ? obj : { <span class="hljs-attr">default</span>: obj }
}
<span class="hljs-built_in">console</span>.log(_a2.default)
<span class="hljs-comment">// a.js</span>
<span class="hljs-built_in">Object</span>.defineProperty(exports, <span class="hljs-string">'__esModule'</span>, {
    <span class="hljs-attr">value</span>: <span class="hljs-literal">true</span>
})
<span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>
exports.default = a
</code></pre><p>Babel 将我们 ES6 的模块化代码转换为了 CommonJS（如果你不熟悉 CommonJS 的话，可以阅读这一章节中关于 <a target="_blank" href="https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bdd0d83f265da615f76ba57" rel="">模块化的知识点</a>） 的代码，但是浏览器是不支持 CommonJS 的，所以如果这段代码需要在浏览器环境下运行的话，我们需要自己实现 CommonJS 相关的代码，这就是 <code>bundle</code> 函数做的大部分事情。</p>
<p>接下来我们再来逐行解析 <code>bundle</code> 函数</p>
<ul>
<li>首先遍历所有依赖文件，构建出一个函数参数对象</li>
<li>对象的属性就是当前文件的相对路径，属性值是一个函数，函数体是当前文件下的代码，函数接受三个参数 <code>module</code>、<code>exports</code>、 <code>require</code>
<ul>
<li><code>module</code> 参数对应 CommonJS 中的 <code>module</code></li>
<li><code>exports</code> 参数对应 CommonJS 中的 <code>module.export</code></li>
<li><code>require</code> 参数对应我们自己创建的 <code>require</code> 函数</li>
</ul>
</li>
<li>接下来就是构造一个使用参数的函数了，函数做的事情很简单，就是内部创建一个 <code>require</code> 函数，然后调用 <code>require(entry)</code>，也就是 <code>require('./entry.js')</code>，这样就会从函数参数中找到 <code>./entry.js</code> 对应的函数并执行，最后将导出的内容通过 <code>module.export</code> 的方式让外部获取到</li>
<li>最后再将打包出来的内容写入到单独的文件中</li>
</ul>
<p>如果你对于上面的实现还有疑惑的话，可以阅读下打包后的部分简化代码</p>
<pre><code class="hljs js" lang="js">;(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">modules</span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">require</span>(<span class="hljs-params">id</span>) </span>{
    <span class="hljs-comment">// 构造一个 CommonJS 导出代码</span>
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">module</span> = { <span class="hljs-attr">exports</span>: {} }
    <span class="hljs-comment">// 去参数中获取文件对应的函数并执行</span>
    modules[id](<span class="hljs-built_in">module</span>, <span class="hljs-built_in">module</span>.exports, <span class="hljs-built_in">require</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">module</span>.exports
  }
  <span class="hljs-built_in">require</span>(<span class="hljs-string">'./entry.js'</span>)
})({
  <span class="hljs-string">'./entry.js'</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">module, exports, require</span>) </span>{
    <span class="hljs-comment">// 这里继续通过构造的 require 去找到 a.js 文件对应的函数</span>
    <span class="hljs-keyword">var</span> _a = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./a.js'</span>)
    <span class="hljs-built_in">console</span>.log(_a2.default)
  },
  <span class="hljs-string">'./a.js'</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">module, exports, require</span>) </span>{
    <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>
    <span class="hljs-comment">// 将 require 函数中的变量 module 变成了这样的结构</span>
    <span class="hljs-comment">// module.exports = 1</span>
    <span class="hljs-comment">// 这样就能在外部取到导出的内容了</span>
    exports.default = a
  }
  <span class="hljs-comment">// 省略</span>
})
</code></pre><h2 class="heading" data-id="heading-2">小结</h2>
<p>虽然实现这个工具只写了不到 100 行的代码，但是打包工具的核心原理就是这些了</p>
<ol>
<li>找出入口文件所有的依赖关系</li>
<li>然后通过构建 CommonJS 代码来获取 <code>exports</code> 导出的内容</li>
</ol>
<p>如果大家对于这个章节的内容存在疑问，欢迎在评论区与我互动。</p>
</div><section data-v-05bbf43a="" class="book-comments"><div data-v-05bbf43a="" class="box-title">留言</div><div data-v-05bbf43a="" class="comment-box"><div data-v-81313b1e="" data-v-05bbf43a="" class="comment-form comment-form" id="comment"><div data-v-3b1ba6d2="" data-v-afcc6e34="" data-v-81313b1e="" data-src="https://b-gold-cdn.xitu.io/v3/static/img/default-avatar.e30559a.svg" class="lazy avatar avatar loaded" style="background-image: url(&quot;https://b-gold-cdn.xitu.io/v3/static/img/default-avatar.e30559a.svg&quot;);"></div><textarea data-v-81313b1e="" placeholder="评论将在后台进行审核，审核通过后对所有人可见" class="content-input" style="overflow: hidden; overflow-wrap: break-word; height: 60px;"></textarea><div data-v-81313b1e="" class="action-box" style="display: none;"><div data-v-680eb36d="" data-v-81313b1e="" class="image-uploader image-uploader" style="display: none;"><input data-v-680eb36d="" type="file" class="input"><button data-v-680eb36d="" class="upload-btn"><i data-v-680eb36d="" class="icon ion-image"></i><span data-v-680eb36d="">上传图片</span></button></div><div data-v-81313b1e="" class="submit-box"><span data-v-81313b1e="" class="submit-text">Ctrl or ⌘ + Enter</span><button data-v-81313b1e="" class="submit-btn">评论</button></div></div><!----></div></div><ul data-v-32e5a55b="" data-v-05bbf43a="" st:block="commentList" class="comment-list comment-list"><!----></ul></section></div></div><!----><!----></div></div><div data-v-a9263a68="" data-v-097468bb="" class="book-handle book-direction"><div data-v-a9263a68="" class="step-btn step-btn--prev"><img data-v-a9263a68="" src="./18-实现小型打包工具_files/prev.87ad47e.svg"></div><div data-v-a9263a68="" class="step-btn step-btn--next"><img data-v-a9263a68="" src="./18-实现小型打包工具_files/next.54d8a35.svg"></div><!----><!----></div><!----></div></div></section><!----><!----><!----><div data-v-1b0b9cb8="" data-v-097468bb="" class="image-viewer-box"><!----></div></div><!----></div>
      
      
      <script type="text/javascript" src="./18-实现小型打包工具_files/runtime.5902a8b1cc2b7567f479.js.下载"></script><script type="text/javascript" src="./18-实现小型打包工具_files/0.e205fcdd4d4b6767f462.js.下载"></script><script type="text/javascript" src="./18-实现小型打包工具_files/1.cbb1f8f5dcdee0086c6a.js.下载"></script>
    </body></html>